import { Checksum, Encoder } from "@smithy/types";
import { toBase64 } from "@smithy/util-base64";
import { buffer, stream } from "@kit.ArkTS";
import { PassThrough } from "../passThrough";

/**
 * @internal
 */
export interface ChecksumStreamInit<T extends stream.Readable > {
  /**
   * Base64 value of the expected checksum.
   */
  expectedChecksum: string;
  /**
   * For error messaging, the location from which the checksum value was read.
   */
  checksumSourceLocation: string;
  /**
   * The checksum calculator.
   */
  checksum: Checksum;
  /**
   * The stream to be checked.
   */
  source: T;

  /**
   * Optional base 64 encoder if calling from a request context.
   */
  base64Encoder?: Encoder;
}

/**
 * @internal
 *
 * Wrapper for throwing checksum errors for streams without
 * buffering the stream.
 *
 */
export class ChecksumStream extends PassThrough{
  private expectedChecksum: string;
  private checksumSourceLocation: string;
  private checksum: Checksum;
  private base64Encoder: Encoder;

  public constructor({
    expectedChecksum,
    checksum,
    source,
    checksumSourceLocation,
    base64Encoder,
  }: ChecksumStreamInit<stream.Readable>) {
    super(source,'ChecksumStream');
    this.base64Encoder = base64Encoder ?? toBase64;
    this.expectedChecksum = expectedChecksum;
    this.checksum = checksum;
    this.checksumSourceLocation = checksumSourceLocation;
    source.pipe(this)
  }

  doPush(chunk: string | Uint8Array, encoding: string, callback: Function): void {
    let buf:Uint8Array|undefined = undefined
    if (typeof chunk === 'string') {
      if (buffer.isEncoding(encoding)) {
        buf = new Uint8Array(buffer.from(chunk,encoding as buffer.BufferEncoding).buffer)
      }else{
        buf = new Uint8Array(buffer.from(chunk).buffer)
      }
    }else{
      buf = chunk
    }
    this.checksum.update(buf);
    this.push(chunk,encoding);
    callback();
  }


  async finally(): Promise<void> {
    const digest: Uint8Array = await this.checksum.digest();
    const received = this.base64Encoder(digest);
    if (this.expectedChecksum !== received) {
      throw new Error(
        `Checksum mismatch: expected "${this.expectedChecksum}" but received "${received}"` +
          ` in response header "${this.checksumSourceLocation}".`
      )
    }
  }
}
